package uk.co.wobblycogs.vegas;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A simple little application which renames the files downloaded from by Sony
 * Vegas Movie Studio from Panasonic camcorders.
 * <p>
 * The files, as downloaded from the camcorder, have a recording number
 * generated by the camcorder at the start of the filename. Since this number is
 * reset each time the camera card is erased many files start with "00000",
 * "00001" etc which breaks the logical sorting in Windows Explorer making it
 * difficult to find a file.
 * <p>
 * This application renames the file to include only the date portion of the
 * filename. It has a default output pattern which broadly follows the ISO 8601
 * standard and a strict mode output pattern which exactly follows the standard
 * but produces a less readable file name. Additionally you can specify your own
 * output pattern.
 * <p>
 * Note: the Vegas pattern should include two named capture groups called index
 * and date.
 *
 */
public class Rename {

	private static final Logger logger = LoggerFactory.getLogger(Rename.class);
	private static final String DEFAULT_VEGAS_PATTERN = "(?<index>\\d{5})_(?<date>((19|20)\\d\\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])_([01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]).mts";
	private static final String DEFAULT_OUTPUT_PATTERN = "yyyy-MM-dd_HHmmss";
	private static final String INDEX_GROUP_NAME = "index";
	private static final String DATE_GROUP_NAME = "date";
	/**
	 * This output pattern is as per the ISO 8601 specification but it's
	 * difficult to read. The extended format for dates would be better but
	 * requires the colon character in the output which is not allow on Windows
	 * file systems.
	 */
	private static final String STRICT_OUTPUT_PATTERN = "yyyyMMddThhmmss";
	private String vegasPattern = DEFAULT_VEGAS_PATTERN;
	private String outputPattern = DEFAULT_OUTPUT_PATTERN;
	private boolean move;
	private Path sourceFolder;

	public static void main(String[] args) throws IOException {
		//Definition
		Options options = new Options();
		options.addOption("h", "help", false, "Prints this help.");
		options.addOption("f", "folder", true, "Folder to scan for files to rename. Required");
		options.addOption("vp", "vegas-pattern", true, "Override for the default Vegas pattern. Optional");
		options.addOption("op", "output-pattern", true, "Override for the default output pattern. Optional");
		options.addOption("m", "move", false, "Move rather than rename. This is much slower but safer as it doesn't touch the original file. Optional;");
		options.addOption("so", "strict-output", false, "Use the strict output pattern. This complies with ISO 8601 but it less readable. Over ridden by the output-pattern setting. Optional");

		//Parsing
		CommandLineParser parser = new PosixParser();
		CommandLine cmd = null;
		try {
			cmd = parser.parse(options, args);
		} catch (ParseException e) {
			e.printStackTrace();
			System.exit(1);
		}

		//Interrogation
		if (cmd.hasOption('h')) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("Rename", options);
			System.exit(0);
		}

		Rename r = new Rename();

		if (!cmd.hasOption("f")) {
			throw new IllegalArgumentException("The folder option must be present.");
		}
		Path folder = Paths.get(cmd.getOptionValue("f"));
		if (!Files.exists(folder) || !Files.isDirectory(folder)) {
			throw new FileNotFoundException("The source folder must exist.");
		}
		r.setSourceFolder(folder);

		if (cmd.hasOption("m")) {
			r.setMove(true);
		}

		if (cmd.hasOption("so")) {
			r.setOutputPattern(STRICT_OUTPUT_PATTERN);
		}

		if (cmd.hasOption("op")) {
			r.setOutputPattern(cmd.getOptionValue("op"));
		}

		if (cmd.hasOption("vp")) {
			r.setVegasPattern(cmd.getOptionValue("vp"));
		}

		r.execute();
	}

	public void execute() throws IOException {
		logger.info("Scanning: {}", getSourceFolder().toString());
		Pattern p = Pattern.compile(getVegasPattern());
		List<Path> paths = new ArrayList<>();
		try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(getSourceFolder(), new DirectoryStream.Filter<Path>() {
			@Override
			public boolean accept(Path entry) throws IOException {
				if (Files.isDirectory(entry)) {
					return false;
				}

				Path fileName = entry.getFileName();
				if (fileName == null) {
					return false;
				}

				Matcher m = p.matcher(fileName.toString());
				boolean matches = m.matches();
				if (matches) {
					logger.debug("Accepting: {}", fileName.toString());
				} else {
					logger.debug("Rejecting: {}", fileName.toString());
				}
				return matches;
			}
		})) {
			for (Path path : directoryStream) {
				paths.add(path);
			}
		} catch (IOException e) {
		}

		DateTimeFormatter vegasTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
		DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(getOutputPattern());

		for (Path path : paths) {
			String fileName = path.getFileName().toString();
			logger.info("Processing: {}", fileName);

			Matcher m = p.matcher(fileName);
			m.matches();
			Integer index = Integer.parseInt(m.group(INDEX_GROUP_NAME));
			LocalDateTime ldt = vegasTimeFormatter.parse(m.group(DATE_GROUP_NAME), LocalDateTime::from);

			logger.debug("Index: {}      Date: {}", index, ldt.toString());

			Path outputPath = path.resolveSibling(outputFormatter.format(ldt) + ".mts");
			logger.debug("Renaming: {}      To: {}", path.toString(), outputPath.toString());
			//Note that this won't try to over write the target if it already exists.
			Files.move(path, outputPath);
		}

	}

	public String getOutputPattern() {
		return outputPattern;
	}

	public void setOutputPattern(String outputPattern) {
		this.outputPattern = outputPattern;
	}

	public String getVegasPattern() {
		return vegasPattern;
	}

	public void setVegasPattern(String vegasPattern) {
		this.vegasPattern = vegasPattern;
	}

	public boolean isMove() {
		return move;
	}

	public void setMove(boolean move) {
		this.move = move;
	}

	public Path getSourceFolder() {
		return sourceFolder;
	}

	public void setSourceFolder(Path sourceFolder) {
		this.sourceFolder = sourceFolder;
	}
}
